using System;
using System.Collections;
using Velocity4Net.Runtime;

namespace Velocity4Net.Ctx
{

    /**
     *  This is a special, internal-use-only context implementation to be
     *  used for the #evaluate directive.
     *
     *  We use this context to chain the existing context, preventing any changes
     *  from impacting the parent context.  By separating this context into a 
     *  separate class it also allows for the future possibility of changing
     *  the context behavior for the #evaluate directive.
     *  
     *  Note that the context used to store values local to #evaluate()
     *  is user defined but defaults to {@link VelocityContext}.
     *
     *  @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
     *  @version $Id: EvaluateContext.java 898032 2010-01-11 19:51:03Z nbubna $
     *  @since 1.6
     *  @deprecated Will be removed in 2.0
     */
    public class EvaluateContext : ChainedInternalContextAdapter
    {
        /** container for any local items */
        IContext localContext;

        /**
        *  CTOR, wraps an ICA
        * @param inner context for parent template
        * @param rsvc 
        */
        public EvaluateContext(InternalContextAdapter inner, RuntimeServices rsvc)
            : base(inner)
        {
            initContext(rsvc);
        }

        /**
         * Initialize the context based on user-configured class 
         * @param rsvc
         */
        private void initContext(RuntimeServices rsvc)
        {
            String contextClass = rsvc.GetString(RuntimeConstants.EVALUATE_CONTEXT_CLASS);

            //if (contextClass != null && contextClass.length() > 0)
            //{
            //    rsvc.getLog().warn("The "+RuntimeConstants.EVALUATE_CONTEXT_CLASS+
            //        " property has been deprecated. It will be removed in Velocity 2.0. "+
            //        " Instead, please use the automatically provided $evaluate"+
            //        " namespace to get and set local references"+
            //        " (e.g. #set($evaluate.foo = 'bar') and $evaluate.foo).");

            //    Object o = null;

            //    try
            //    {
            //        o = ClassUtils.getNewInstance( contextClass );
            //    }
            //    catch (ClassNotFoundException cnfe)
            //    {
            //        String err = "The specified class for #evaluate() context (" + contextClass
            //        + ") does not exist or is not accessible to the current classloader.";
            //        rsvc.getLog().error(err);
            //        throw new System.ApplicationException(err,cnfe);
            //    }
            //    catch (System.Exception e)
            //    {
            //        String err = "The specified class for #evaluate() context (" + contextClass
            //        + ") can not be loaded.";
            //        rsvc.getLog().error(err,e);
            //        throw new System.ApplicationException(err);
            //    }

            //    if (!(o is IContext))
            //    {                
            //        String err = "The specified class for #evaluate() context (" + contextClass
            //        + ") does not implement " + typeof(IContext).Name + ".";
            //        rsvc.getLog().error(err);
            //        throw new System.ApplicationException(err);
            //    }

            //    localContext = (Context) o; 

            //}
            //else
            //{
            //    if (rsvc.getLog().isDebugEnabled())
            //    {
            //        rsvc.getLog().debug("No class specified for #evaluate() context, "+
            //            "so #set calls will now alter the global context and no longer be local.  "+
            //            "This is a change from earlier versions due to VELOCITY-704.  "+
            //            "If you need references within #evaluate to stay local, "+
            //            "please use the automatically provided $evaluate namespace instead "+
            //            "(e.g. #set($evaluate.foo = 'bar') and $evaluate.foo).");
            //    }
            //}

        }

        /**
         *  Put method also stores values in local scope 
         *
         *  @param key name of item to set
         *  @param value object to set to key
         *  @return old stored object
         */
        public Object put(String key, Object value)
        {
            if (localContext != null)
            {
                return localContext.Put(key, value);
            }
            return base.Put(key, value);
        }

        /**
         *  Retrieves from local or global context.
         *
         *  @param key name of item to get
         *  @return  stored object or null
         */
        public Object get(String key)
        {
            /*
             *  always try the local context then innerContext
             */
            Object o = null;
            if (localContext != null)
            {
                o = localContext.Get(key);
            }
            if (o == null)
            {
                o = base.Get(key);
            }
            return o;
        }

        /**
         * @see org.apache.velocity.context.Context#containsKey(java.lang.Object)
         */
        public bool ContainsKey(Object key)
        {
            return (localContext != null && localContext.ContainsKey(key)) ||
                   base.ContainsKey(key);
        }

        /**
         * @see org.apache.velocity.context.Context#getKeys()
         */
        public ICollection Keys
        {
            //if (localContext != null)
            //{
            //    Set keys = new HashSet();
            //    Object[] localKeys = localContext.getKeys();
            //    for (int i=0; i < localKeys.length; i++)
            //    {
            //        keys.add(localKeys[i]);
            //    }

            //    Object[] innerKeys = super.getKeys();
            //    for (int i=0; i < innerKeys.length; i++)
            //    {
            //        keys.add(innerKeys[i]);
            //    }
            //    return keys.toArray();
            //}
            get
            {
                return base.Keys;
            }
        }

        /**
         * @see org.apache.velocity.context.Context#remove(java.lang.Object)
         */
        public Object remove(Object key)
        {
            if (localContext != null)
            {
                return localContext.Remove(key);
            }
            return base.Remove(key);
        }

        /**
         * Allows callers to explicitly put objects in the local context.
         * Objects added to the context through this method always end up
         * in the top-level context of possible wrapped contexts.
         *
         *  @param key name of item to set.
         *  @param value object to set to key.
         *  @return old stored object
         */
        public Object LocalPut(String key, Object value)
        {
            if (localContext != null)
            {
                return localContext.Put(key, value);
            }
            return base.LocalPut(key, value);
        }

    }
}